home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2006 December / PCWDEC06.iso / Software / Trial / Paint Shop Pro XI / Data1.cab / pspscriptparse.py < prev    next >
Encoding:
Python Source  |  2006-08-04  |  30.4 KB  |  842 lines

  1. # PSPScriptParse.py...  This file does the parsing for the script editor.
  2. #
  3.  
  4. import sys, re, string
  5. import PSPScriptMsgs
  6. import PSPScriptPyBlocks
  7.  
  8.  
  9. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  10. #
  11. #   this section contains the re pattern matching statements...
  12. #
  13. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  14.  
  15. PattImportPSP = re.compile(r"\bfrom\s*PSPApp\s*import\s*\*")
  16. PattImportJasc = re.compile(r"\bfrom\s*JascApp\s*import\s*\*")
  17. PattImportGeneral = re.compile(r"\bfrom\s*\w*\s*import\s*\*")
  18. PattDefDo = re.compile(r"\bdef\s*Do\(Environment\)\:")
  19. PattScriptProp = re.compile(r"\bdef\s*ScriptProperties\(\)\:\s*")
  20. PattScriptPropLine2 = re.compile(r"\s*\breturn\s*{\s*")
  21. PattBlankLine = re.compile(r"\s*\n")
  22. PattCommentLine = re.compile(r"\s*#")
  23. PattCloseParen = re.compile(r"\s*\)")
  24. PattDocBlock = re.compile(r"\s*'''")
  25. PattCloseBracket = re.compile(r"\s*\}")
  26. PattBlankCmtLine = re.compile("\s*#\s*\n")
  27. PattCloseBracketCmtLine = re.compile("\s*#\s*}\)")
  28. #   match PSPCommand.
  29. #       group(0) -> entire string
  30. #       group(1) -> App.do(
  31. #       group(2) -> quoted PSP Command Name
  32. #       group(3) -> {
  33. PattPSPCmd = re.compile(r"(\s*App\.Do\(\s*Environment,\s*)" +
  34.                         r"(\'[\w*\s]+\')+\," +
  35.                         r"(\s*{\s*)")
  36. PattCR = re.compile(r"\s*\n")
  37. PattBracketParen = re.compile(r"\s*}\)")
  38.  
  39. #   match simple PSP parameter.  Match through the ':'
  40. #       group(1) -> parameter name
  41. #  PattPSPParmSimple = re.compile(r"(\s*\'\s??\w*\s??\w*\')+\s*:\s*")
  42. '''
  43. PattPSPParmSimple = re.compile(r"(\s*\')" +
  44.                                 r"(\s??\w*\s??\w*)" +
  45.                                 r"\'+\s*:\s*")
  46. '''
  47.  
  48. PattPSPParmSimple = re.compile(r"(\s*\')" +
  49.                                r"([\w*\s]+)" +
  50.                                r"\'+\s*:\s*")
  51.  
  52. #   match PSP parameter Start Dictionary.  Find parm name and match
  53. #       up through the opening brace
  54. #       group(1) -> parameter name
  55. PattPSPParmStrDict = re.compile(r"(\s*\'\w*\')+\s*:\s*{")
  56.  
  57.  
  58. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  59. # Class: ParseScript
  60. # Author: Carl Lestor
  61. # Purpose: This class handles the parsing of the python script file.  Its main job
  62. #   is to parse the file and build up 'pyBlocks'.  These objects represent blocks
  63. #   of python code, psp commands, script descriptions and the like.  
  64. # Returns: none
  65. # Parameters:   fp - file handle for the file to parse.
  66. #               pyBlockPtr = place to store the python blocks parsed   
  67. # Notes: Errors are sent to std out; the script output window.
  68. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  69. class ParseScript:
  70.     def __init__(self):
  71.         self.pyBlocks = []      # a place to pack hold the python blocks representing the script
  72.         self.fp = 0             # handle of the file
  73.         self.line = ""          # line being parsed
  74.         self.block = ""         # blocks of code built up during parse
  75.         self.linepos = 0        # line position during parse
  76.         #print 'IN PARSE SCRIPT'
  77.  
  78.         # following data is used for understanding parse failures
  79.         self.prevLine = ""      # previous line
  80.         self.prevPrevLine = ""  # previous previous line
  81.         self.lineNum = 0        # current line being parsed
  82.         self.errorString = ""   # set when a parse fails
  83.         self.filename = ""      # store the file name in case we need to invoke a text editor
  84.  
  85. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  86. # Class: ParseScript::openFile
  87. # Author: Steve Neumeyer
  88. # Purpose: initialize the file pointer for reading
  89. # Returns: none
  90. # Parameters:   name - the filename to open
  91. # Notes: Simply opens the file and stores the reference in the file member
  92. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  93.     def openFile(self, name):
  94.         try:
  95.             self.filename = name
  96.             self.fp = open(name, 'r')
  97.             return 1
  98.         except:
  99.             return 0        
  100.  
  101. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  102. # Class: ParseScript::getLine
  103. # Author: Carl Lestor
  104. # Purpose: get a new line of text from the file
  105. # Returns: none
  106. # Parameters:   cmtflag - indicates if we are handling a commented psp command
  107. # Notes: Much of the parsing shares the logic if we are parsing a commented psp
  108. #       command or a non-commented one; hence the input flag.  This method
  109. #       silently skips the splat if we are doing comments.
  110. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  111.     def getLine(self, cmtFlag):
  112.         # if requested, handle comment lines by moving the line position past the opening comment char
  113.         self.prevPrevLine = self.prevLine       # save prev lines for diagnosis...
  114.         self.prevLine = self.line
  115.         self.line = self.fp.readline()
  116.         self.linepos = 0
  117.         self.lineNum = self.lineNum + 1
  118.         if cmtFlag:
  119.             # caller indicated we are in a state parsing commented material.  skip over the comment
  120.             # portion of the line.  Return 0 if we do not find a comment....
  121.             match = PattCommentLine.match(self.line)
  122.             if match:
  123.                 self.linepos = match.end(0)
  124.                 return (self.line)
  125.             else:
  126.                 return 0
  127.         else:
  128.             return (self.line)
  129.         
  130.  
  131. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  132. # Class: ParseScript::addPyBlock
  133. # Author: Carl Lestor
  134. # Purpose: get a new line of text from the file
  135. # Returns: none
  136. # Parameters:   type - type of block to add.
  137. # Notes: 
  138. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  139.     def addPyBlock(self, type):
  140.         # set up the block that was found.
  141.         pyb = PSPScriptPyBlocks.PyBlock(type, self.block)
  142.         self.pyBlocks = self.pyBlocks + [pyb]
  143.         self.block = ""
  144.  
  145.  
  146. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  147. # Class: ParseScript::parseFail
  148. # Author: Carl Lestor
  149. # Purpose: simply a place to handle parse errors consistently.
  150. # Returns: none
  151. # Parameters:   error.  String describing the type of error ecountered.  Abstracted to
  152. #       a unique file for translation ease.
  153. # Notes: 
  154. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  155.     def parseFail(self, error):
  156.         # the parse failed...
  157. ##        print 'In parseFail, error = %s' % error
  158.         self.errorString = PSPScriptMsgs.PSPSEmsgs(error)
  159.  
  160. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  161. # Class: ParseScript::getNextNonEscapedQuoteOld
  162. # Author: Steve Neumeyer
  163. # Purpose: get the next quote that is not escaped with a \ preceeding it
  164. # Returns: the position of the next non-escaped quote mark
  165. #            -1 for error condition
  166. # Parameters:   currentPosition - the current position in the string
  167. #                string - the line of text being searched
  168. # Notes: this could be moved to PSPTools
  169. #            this version only handles single quote marks '
  170. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  171.     def getNextNonEscapedQuoteOld(self, currentPosition, str):
  172.         escapedQuote = "\\'"
  173.  
  174.         while 1:
  175.             nextQuotePos = string.find(str, "'", currentPosition)
  176.             nextEscapedQuotePos = string.find(str, escapedQuote, currentPosition)
  177.             if nextEscapedQuotePos != -1:
  178.                 nextEscapedQuotePos += 1 
  179.  
  180.             # if the next quote is escaped, search forward from that position until
  181.             # we find a non-escaped quote
  182.             if nextQuotePos == nextEscapedQuotePos:
  183.                 currentPosition = 1+nextQuotePos # now we are searching from the last found quote pos
  184.                 continue
  185.             else:
  186.                 return nextQuotePos
  187.  
  188. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  189. # Class: ParseScript::getNextNonEscapedQuote
  190. # Author: Steve Neumeyer
  191. # Purpose: get the next quote mark ' or " that is not escaped with a \ preceeding it
  192. # Returns: the position of the next non-escaped quote mark
  193. #            -1 for error condition
  194. # Parameters:   currentPosition - the current position in the string
  195. #                string - the line of text being searched
  196. #                doublequote - 1 or nonzero - we are dealing with a double quote
  197. #                            - 0 - single quote
  198. # Notes: updated version to handle ' and " quotes
  199. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  200.     def getNextNonEscapedQuote(self, currentPosition, str, doublequote):
  201.         if doublequote:
  202.             escapedQuote = r'\"'
  203.         else:
  204.             escapedQuote = r"\'"
  205.  
  206.         while 1:
  207.             if doublequote:
  208.                 nextQuotePos = string.find(str, '"', currentPosition)                
  209.             else:
  210.                 nextQuotePos = string.find(str, "'", currentPosition)
  211.  
  212.  
  213.             nextEscapedQuotePos = string.find(str, escapedQuote, currentPosition)
  214.             if nextEscapedQuotePos != -1:
  215.                 nextEscapedQuotePos += 1 
  216.  
  217.             # if the next quote is escaped, search forward from that position until
  218.             # we find a non-escaped quote
  219.             if nextQuotePos == nextEscapedQuotePos:
  220.                 currentPosition = 1+nextQuotePos # now we are searching from the last found quote pos
  221.                 continue
  222.             else:
  223.                 return nextQuotePos
  224.  
  225. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  226. # Class: ParseScript::EatValue
  227. # Author: Carl Lestor
  228. # Purpose: eats the value portion of a dictionary
  229. #       entry.  We simply consume all characters until the
  230. #       comma indicating the value has ended.  We must ignore
  231. #       commas embedded in tuples, dicts, and lists....  When
  232. #       successful, we add the value to the codeblock.
  233. # Returns:  success - 1 if good, 0 if parse fails
  234. #           moreLeft - 1 if true, 0 if end of dictionary
  235. #           Value - value parsed
  236. # Parameters:  cmtFlag - indicates if we are parsing a comment. 
  237. # Notes: 
  238. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  239.     def EatValue(self, cmtFlag):
  240.  
  241.         startoff = self.linepos
  242.         fragToAdd = self.line[startoff:len(self.line)]
  243.         Value = ''
  244.  
  245.         embedded = 0
  246.         stringEating = 0
  247.         skipNextStrQuotePos = 0
  248.         blobEating = 0
  249.         if self.line[self.linepos] == "'":
  250.             doubleQuote = 0
  251.         elif self.line[self.linepos] == '"':
  252.             doubleQuote = 1
  253.         else:
  254.             doubleQuote = 0
  255.             
  256.         # store the position of the final quote mark in a string
  257.         # this is an index to the last ' or " in a string, depending
  258.         # on what type of quote we started with.  default index is
  259.         # the position of the last ' mark
  260.         #finalQuotePos = string.rfind(self.line, "'")
  261.         nextQuotePos = self.getNextNonEscapedQuote(self.linepos+1, self.line, doubleQuote)
  262.         while 1:
  263.             # get the character and process it....                
  264.             ch = self.line[self.linepos]
  265.  
  266.             if blobEating:
  267.                 # Blobs start and end with triple single quotes - everything else is part of the value
  268.                 if ch == '\0' or ch == '\n':
  269.                     # New Line in BLOB
  270.                     Value = Value + fragToAdd[0:self.linepos + 1 - startoff]
  271.                     self.getLine(cmtFlag)
  272.                     if not self.line:
  273.                         # we hit eof in error. incomplete blob
  274.                         self.block = self.block + Value
  275.                         return (0, 0, Value)
  276.                     fragToAdd = self.line
  277.                     self.linepos = 0
  278.                     startoff = self.linepos
  279.                 elif ch =="\'": 
  280.                     nextQuotePos = self.getNextNonEscapedQuote(self.linepos+1, self.line, 0)
  281.                     self.linepos = self.linepos + 1
  282.                     if nextQuotePos == self.linepos and self.linepos + 1 == self.getNextNonEscapedQuote(self.linepos+1, self.line, 0):
  283.                         # Three quotes in a row - End of the blob
  284.                         self.linepos = self.linepos + 2
  285.                         nextQuotePos = self.getNextNonEscapedQuote(self.linepos, self.line, 0)
  286.                         blobEating = 0
  287.                 else:
  288.                     self.linepos = self.linepos + 1
  289.             elif stringEating:
  290.                 # handle strings... end chars in strings must be ignored...            
  291.                 if ch == '\0' or ch == '\n':                
  292.                     # New Line in String
  293.                     Value = Value + fragToAdd[0:self.linepos - 2 - startoff]
  294.                     self.getLine(cmtFlag)
  295.                     if not self.line:
  296.                         # we hit eof in error. incomplete string
  297.                         self.block = self.block + Value
  298.                         return (0, 0, Value)
  299.                     self.linepos = 0
  300.                     skipNextStrQuotePos = 1
  301.                 elif skipNextStrQuotePos and (ch == "\'" or ch == "\""):
  302.                         skipNextStrQuotePos = 0
  303.                         startoff = self.linepos + 1
  304.                         nextQuotePos = self.getNextNonEscapedQuote(self.linepos+1, self.line, not singleQuote)
  305.                         fragToAdd = self.line[startoff:len(self.line)]
  306.                 elif singleQuote and ch == "\'" and self.linepos == nextQuotePos: #finalQuotePos:
  307.                         stringEating = 0
  308.                         singleQuote = 0
  309.                 elif not singleQuote and ch == '\"' and self.linepos == nextQuotePos: #finalQuotePos:
  310.                         stringEating = 0
  311.                 self.linepos = self.linepos + 1
  312.             elif ch =="\'": 
  313.                 stringEating = 1
  314.                 singleQuote = 1
  315.                 #finalQuotePos = string.rfind(self.line, "'")
  316.                 nextQuotePos = self.getNextNonEscapedQuote(self.linepos+1, self.line, 0)
  317.                 self.linepos = self.linepos + 1
  318.                 if nextQuotePos == self.linepos and self.linepos + 1 == self.getNextNonEscapedQuote(self.linepos+1, self.line, 0):
  319.                     # Three quotes in a row - Start of a blob
  320.                     self.linepos = self.linepos + 2
  321.                     nextQuotePos = self.getNextNonEscapedQuote(self.linepos, self.line, 0)
  322.                     stringEating = 0
  323.                     singleQuote = 0
  324.                     blobEating = 1
  325.             elif ch =='\"': 
  326.                 stringEating = 1
  327.                 singleQuote = 0
  328.                 #finalQuotePos = string.rfind(self.line, '"')
  329.                 nextQuotePos = self.getNextNonEscapedQuote(self.linepos+1, self.line, 1)
  330.                 self.linepos = self.linepos + 1
  331.             # check for ending characters                                    
  332.             elif ch =="," and embedded == 0:
  333.                 # end of value, more dictionary to handle....
  334.                 Value = Value + fragToAdd[0:self.linepos - startoff]
  335.                 self.block = self.block + Value + ch
  336.                 self.linepos = self.linepos + 1
  337.                 self.chkEOLN(cmtFlag)
  338.                 return (1, 1, Value)
  339.             elif ch == "}" and embedded == 0:
  340.                 # end of value, end of dictionary....
  341.                 Value = Value + fragToAdd[0:self.linepos - startoff]
  342.                 self.block = self.block + Value + ch
  343.                 self.linepos = self.linepos + 1
  344.                 self.chkEOLN(cmtFlag)
  345.                 return (1, 0, Value)
  346.  
  347.             # handle being imbedded in dicts, tuples, or lists...            
  348.             elif (ch == '(' or ch =='{' or ch == '['):
  349.                 embedded = embedded + 1
  350.                 self.linepos = self.linepos + 1
  351.             elif ch == ')' or ch =='}' or ch == ']':
  352.                 embedded = embedded - 1
  353.                 self.linepos = self.linepos + 1
  354.                  
  355.             # go right through EOLN for longer parms...
  356.             elif ch == '\0' or ch == '\n':
  357.                 # we hit end of line
  358.                 Value = Value + fragToAdd[0:self.linepos + 1 - startoff]
  359.                 self.getLine(cmtFlag)
  360.                 if not self.line:
  361.                     # we hit eof in error. incomplete dict or imbalanced parens
  362.                     self.block = self.block + Value
  363.                     return (0, 0, Value)
  364.                 fragToAdd = self.line
  365.                 self.linepos = 0
  366.                 startoff = self.linepos
  367.             else:
  368.                 # any old character
  369.                 self.linepos = self.linepos + 1
  370.  
  371.  
  372. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  373. # Class: ParseScript::EatDocBlock
  374. # Author: Carl Lestor
  375. # Purpose: eats the contents of a triple quoted documentation block
  376. #       The first triple quoted chars have been found..  When
  377. #       successful, we add the value to the codeblock.
  378. # Returns: success - 1 if good, 0 if parse failure
  379. # Parameters:   none
  380. # Notes: 
  381. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  382.     def eatDocBlock(self):
  383.  
  384.         startoff = self.linepos
  385.         fragToAdd = self.line[startoff:len(self.line)]
  386.         singleQuoteDepth = 0
  387.       
  388.         while singleQuoteDepth < 3:
  389.             ch = self.line[self.linepos]
  390.             self.linepos = self.linepos + 1
  391.  
  392.             if ch == "\'":
  393.                 singleQuoteDepth = singleQuoteDepth + 1
  394.  
  395.             # handle multi line doc blocks...
  396.             elif ch == '\0' or ch == '\n':
  397.                 # we hit end of line
  398.                 self.block = self.block + fragToAdd[0:self.linepos + 1 - startoff]
  399.                 self.getLine(0)
  400.                 if not self.line:
  401.                     # we hit eof in error. incomplete doc block.
  402.                     return (0)
  403.                 fragToAdd = self.line
  404.                 self.linepos = 0
  405.                 startoff = 0
  406.                 singleQuoteDepth = 0
  407.             else:
  408.                 # any old character
  409.                 singleQuoteDepth = 0
  410.         
  411.         # end of doc block found....
  412.         self.block = self.block + fragToAdd[0:self.linepos + 1 - startoff]
  413.         self.chkEOLN(0)
  414.         return (1)
  415.  
  416.  
  417. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  418. # Class: ParseScript::EatDocBlock
  419. # Author: Carl Lestor
  420. # Purpose: check for end of line.  consume white space as we go...
  421. # Returns: 
  422. # Parameters: chkCmtFlag - indicates if we are parsing a comment or not.
  423. # Notes: 
  424. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  425.     def chkEOLN(self, chkCmtFlag):
  426.         
  427.         while 1:
  428.             ch = self.line[self.linepos]
  429.             if ch == " " or ch == "\n":
  430.                 # consume it
  431.                 self.linepos = self.linepos + 1
  432.                 self.block = self.block + ch
  433.                 if self.linepos == len(self.line):
  434.                     # we have reached the end of line.
  435.                     self.getLine(chkCmtFlag)
  436.                     if (chkCmtFlag):
  437.                         # we found a new commented line. need to pick up the splat...
  438.                         self.block = self.block + self.line[0:self.linepos]
  439.                     return (1)
  440.             else: 
  441.                 return (1)
  442.     
  443.  
  444. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  445. # Class: ParseScript::ParseDictionary
  446. # Author: Carl Lestor
  447. # Purpose: parse a dictionary.  
  448. # Returns: success - 1 if good, 0 if parse failure
  449. # Parameters:   OurParms - dictionary or parms being built.  
  450. #               cmtFlag - indicates if we are parsing a comment or not.
  451. # Notes: Finds a dictionary entry and adds it to OurParms
  452. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  453.     def parseDictionary(self, OurParms, cmtFlag):
  454.  
  455.         # assumes open brace has been consumed. match all
  456.         # and consume the end brace.
  457.         while 1:
  458.             # we either have consumed the line with the re match or there are some
  459.             #   parms also on that line.  if at the end, get a new one....
  460.             if self.linepos >= len(self.line):
  461.                 self.getLine(cmtFlag)
  462.                 if not self.line:
  463.                     return 0
  464.                 if (cmtFlag):
  465.                     # we found a new commented line. need to pick up the splat...
  466.                     self.block = self.block + self.line[0:self.linepos]
  467.             # find a parmName:value pair.  first the name.
  468.             match = PattPSPParmSimple.match(self.line, self.linepos)
  469.             if match:
  470.                 # parmName found.  update block and get value.  
  471.                 self.block = self.block + match.group(0)
  472.                 self.linepos = match.end(0)                
  473.                 self.chkEOLN(cmtFlag)
  474.                 ok, moreLeft, Value = self.EatValue(cmtFlag)
  475.                 if not ok:
  476.                     return 0
  477.  
  478.                 # need to trim the spaces from match group1
  479.                 OurParms.append([match.group(2), Value])    
  480.                 if not moreLeft:
  481.                     return 1
  482.             else:
  483.                 return 0
  484.  
  485.             
  486. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  487. # Class: ParseScript::checkState
  488. # Author: Carl Lestor
  489. # Purpose: checks to see if the statement found is valid in the current state
  490. # Returns: 
  491. # Parameters:   state - current state of the parse
  492. #               StatementFound - indicates what type of statement was discovered.
  493. # Notes: The purpose of this method is to provide the user with a more meaningful
  494. #   error msg if the script obviously will not run.  Hopefully this will aid users
  495. #   new to hand editing scripts.  When they invoke the Script Editor, some simple
  496. #   error will be discovered.  This is light and not thorough syntax checking.
  497. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  498.     def checkState(self, state, StatementFound):
  499.  
  500.         #
  501.         #   state tables.  for each state, the dictionary contains the pair of 'received statement type' and
  502.         #       'new state'.  If the new type contains 'ERROR:' in it. the parse failed.  Error message text
  503.         #       is isloated in PSPScriptMsgs for translation....
  504.         #
  505.         ExpectImport = {"Import":"DefDo",
  506.                         "Comment":"Import",
  507.                         "Script":"ERROR:Parse11",
  508.                         "CmtPSPCmd":"ERROR:Parse12",
  509.                         "DefDo":"ERROR:Parse13",
  510.                         "PSPCmd":"ERROR:Parse14"}
  511.  
  512.         ExpectDefDo = {"Import":"ERROR:Parse21",
  513.                         "Comment":"DefDo",
  514.                         "Script":"DefDo",
  515.                         "CmtPSPCmd":"ERROR:Parse22",
  516.                         "DefDo":"PSPCmd",
  517.                         "PSPCmd":"ERROR:Parse23"}
  518.         
  519.         ExpectPSPCmd = {"Import":"ERROR:Parse31",
  520.                         "Comment":"PSPCmd",
  521.                         "Script":"PSPCmd",
  522.                         "CmtPSPCmd":"PSPCmd",
  523.                         "DefDo":"ERROR:Parse32",
  524.                         "PSPCmd":"PSPCmd"}
  525.  
  526.         if state == "Import":
  527.             newstate = ExpectImport[StatementFound]
  528.         elif state == "DefDo":
  529.             newstate = ExpectDefDo[StatementFound]
  530.         else:
  531.             newstate = ExpectPSPCmd[StatementFound]
  532.                             
  533.         if newstate.count("ERROR"):
  534.             return (0, newstate)
  535.         else:
  536.             return (1, newstate)
  537.             
  538.  
  539.     # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  540.     # Class: ParseScript::parseScript
  541.     # Author: Carl Lestor
  542.     # Purpose: This is the main method that parses the script.
  543.     # Returns:
  544.     #           success - 1 if good, 0 if bad
  545.     #           PSPCmdCount - number of psp commands found
  546.     # Parameters:   none
  547.     # Notes: The method is a basic loop processing a line at a time of the input file.  Top
  548.     #   level line recognition is done with re pattern matching.  Outer loop looks for
  549.     #   basic types of statements and then parses the details.
  550.     # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  551.     def parseScript(self):
  552.  
  553.         PSPCmdCount = 0
  554.         ExpectedStmt = "Import"
  555.         
  556.         while 1:
  557.             self.getLine(0)
  558.             if not self.line:
  559.                 return (1, PSPCmdCount)
  560.             #
  561.             # try to match our line to one of our patterns....  first the import statement
  562.             #
  563.             match = PattImportPSP.match(self.line)
  564.             if match:
  565.                 parseOK, ExpectedStmt = self.checkState(ExpectedStmt, "Import")
  566.                 if not parseOK:
  567.                     self.parseFail(ExpectedStmt)
  568.                     return (0, PSPCmdCount)
  569.                 self.block = self.line
  570.                 self.addPyBlock ("ImportPSP")
  571.                 continue
  572.             match = PattImportJasc.match(self.line)
  573.             if match:
  574.                 parseOK, ExpectedStmt = self.checkState(ExpectedStmt, "Import")
  575.                 if not parseOK:
  576.                     self.parseFail(ExpectedStmt)
  577.                     return (0, PSPCmdCount)
  578.                 self.block = self.line
  579.                 self.addPyBlock ("ImportPSP")
  580.                 continue
  581.             #
  582.             # match a def do statement
  583.             #
  584.             match = PattDefDo.match(self.line)
  585.             if match:
  586.                 parseOK, ExpectedStmt = self.checkState(ExpectedStmt, "DefDo")
  587.                 if not parseOK:
  588.                     self.parseFail(ExpectedStmt)
  589.                     return (0, PSPCmdCount)
  590.                 self.block = self.line
  591.                 self.addPyBlock ("DefDo Match")
  592.                 continue
  593.             #
  594.             # match the script properties
  595.             #
  596.             match = PattScriptProp.match(self.line)
  597.             if match:
  598.                 parseOK, ExpectedStmt = self.checkState(ExpectedStmt, "Script")
  599.                 if not parseOK:
  600.                     self.parseFail(ExpectedStmt)
  601.                     return (0, PSPCmdCount)
  602.                 self.block = self.line
  603.                 # line 1 matches.  match line2.
  604.                 self.getLine(0)
  605.                 self.linepos = 0
  606.                 match = PattScriptPropLine2.match(self.line)
  607.                 if match:
  608. #                    self.block = self.line
  609.                     self.block = self.block + match.group(0)
  610.                     self.linepos = match.end(0)
  611.                     parmList = []
  612.                     if self.parseDictionary(parmList, 0):
  613.                         self.addPyBlock ("Script Properties")
  614. #                        print "parmList = ", parmList      # may not need parmList of dict items...
  615.                     else:
  616.                         # failed to parse script properties parameters
  617.                         self.parseFail("ERROR:Parse41")
  618.                         return (0, PSPCmdCount)
  619.                 else:
  620.                     # failed to parse script properties return statement
  621.                     self.parseFail("ERROR:Parse42")
  622.                     return (0, PSPCmdCount)
  623.             #
  624.             # match a psp command
  625.             #
  626.             match = PattPSPCmd.match(self.line)
  627.             if match:
  628.                 parseOK, ExpectedStmt = self.checkState(ExpectedStmt, "PSPCmd")
  629.                 if not parseOK:
  630.                     self.parseFail(ExpectedStmt)
  631.                     return (0, PSPCmdCount)
  632.                 # set the end to the last match position.  group 3....
  633.                 self.block = match.group(0)
  634.                 self.linepos = match.end(0)
  635.                 parmList = []
  636.                 if self.parseDictionary(parmList, 0):
  637.                     # we have parsed the dictionary. pick up the trailing paren
  638.                     match = PattCloseParen.match(self.line, self.linepos)
  639.                     if match:
  640.                         self.block = self.block + match.group(0)
  641.                         self.linepos = match.end(0)
  642.                     self.addPyBlock ("PSPCmd")
  643.                     PSPCmdCount = PSPCmdCount + 1
  644.                 else:
  645.                     # need to handle the case of a command with no dict/repository
  646.                     # match = PattCloseBracket.match(self.line, self.linepos)
  647.                     match = PattCR.match(self.line, self.linepos)
  648.                     # the command has no dict/repository
  649.                     if match: 
  650.                         self.block = self.block + match.group(0)
  651.                         self.linepos = match.end(0)
  652.                         self.getLine(0)
  653.                         match = PattBracketParen.match(self.line, self.linepos)
  654.                         if match:
  655.                             self.block = self.block + match.group(0)
  656.                             self.linepos = match.end(0)
  657.                         else:    # error
  658.                             self.parseFail("ERROR:Parse51")
  659.                             return (0, PSPCmdCount)
  660.                         self.addPyBlock("PSPCmd")
  661.                         PSPCmdCount += 1
  662.                     else:   
  663.                         # failed to parse psp command parameter dictionary
  664.                         
  665.                         # csl...  this error msg is non optimal.  mismatched parens can cause the parse
  666.                         #       to go deep into the file.  line shown in error obj could be far away from bad
  667.                         #       parm list.  (looking for ending paren down the file.)  improvement to add pspcmd to
  668.                         #       error msg....
  669.                     
  670.                         self.parseFail("ERROR:Parse51")
  671.                         return (0, PSPCmdCount)
  672.                 continue
  673.  
  674.             #
  675.             # match a commented line or commented psp command
  676.             #
  677.             match = PattCommentLine.match(self.line)
  678.             if match:
  679.                 self.linepos = match.end(0)
  680.                 if ExpectedStmt == "PSPCmd":
  681.                     # could be a commented psp command.  better check for that.  else just a cmt....
  682.                     self.block = self.block + match.group(0)
  683.                     keepLookingFrom = match.end(0)
  684.                     match = PattPSPCmd.match(self.line, keepLookingFrom)
  685.                     if match:
  686.                         # we have found a comment that starts off like a psp command.  check the parms.
  687.                         self.block = self.block + match.group(0)
  688.                         self.linepos = match.end(0)
  689.                         parmList = []
  690.                         if self.parseDictionary(parmList, 1):
  691.                             # we have parsed the dictionary. pick up the trailing paren
  692.                             match = PattCloseParen.match(self.line, self.linepos)
  693.                             if match:
  694.                                 self.block = self.block + match.group(0)
  695.                                 self.linepos = match.end(0)
  696.                                 self.addPyBlock ("CmtedPSPCmd")
  697.                                 PSPCmdCount = PSPCmdCount + 1
  698.                                 continue
  699.                         else: # commented command with empty dict/repository (ex: UndoLastCmd)
  700.                             match = PattBlankCmtLine.match(self.line, 0)
  701.                             if match:
  702.                                 self.block = self.block + match.group(0)[1:]
  703.                                 self.linepos = match.end(0)
  704.                                 self.getLine(1)
  705.                                 match = PattCloseBracketCmtLine.match(self.line, 0)
  706.                                 if match:
  707.                                     self.block = self.block + match.group(0)
  708.                                     self.linepos = match.end(0)
  709.                                     self.addPyBlock("CmtedPSPCmd")
  710.                                     PSPCmdCount += 1
  711.                                     continue
  712.                                 else:
  713.                                     self.parseFail("ERROR:Parse51")
  714.                                     return (0, PSPCmdCount)
  715.                             else:
  716.                                 self.parseFail("ERROR:Parse51")
  717.                                 return (0, PSPCmdCount)
  718.                     else:
  719.                         self.block = ''
  720.  
  721.                 # csl.... there are several ways to fall through to this.  UT all...        
  722.                 # ended up being a typical comment
  723.                 # self.block = self.block + self.line[self.linepos:len(self.line)]
  724.                 self.block = self.block + self.line[0:len(self.line)]
  725.                 self.addPyBlock ("PythonCmt")
  726.                 continue
  727.  
  728.             #
  729.             # match a doc block (python within a triple quoted block..)
  730.             #
  731.             match = PattDocBlock.match(self.line)
  732.             if match:
  733.                 self.block = match.group(0)
  734.                 self.linepos = match.end(0)
  735.                 if self.eatDocBlock():
  736.                     self.addPyBlock ("DocBlock")
  737.                 else:
  738.                     # failed to find the end of doc block
  739.                     self.parseFail("ERROR:Parse52")
  740.                     return (0, PSPCmdCount)
  741.                 continue
  742.  
  743.             #
  744.             # match a blank or new line.  these are allowed anywhere...
  745.             #
  746.             match = PattBlankLine.match(self.line)
  747.             if match:
  748.                 self.block = self.line
  749.                 self.addPyBlock ("BlankLine")
  750.                 continue
  751.             
  752.             else:
  753.                 #
  754.                 # everything else is a general python code block
  755.                 self.block = self.line
  756.                 self.addPyBlock ("PyCode")
  757.  
  758.  
  759. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  760. # Function: DoParse
  761. # Author: Steve Neumeyer
  762. # Purpose: Do a parse and return the result
  763. # Returns: the result of the parse
  764. # Parameters: fname - file to parse
  765. # Notes: 
  766. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  767. def DoParse(fname, sp):
  768.  
  769.     parseResult = 0
  770.     if sp.openFile(fname):
  771.         parseResult = sp.parseScript()
  772.  
  773.     return sp, parseResult        
  774.                 
  775.  
  776. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  777. # Function: DriveParse
  778. # Author: Carl Lestor
  779. # Purpose: This is a utility function to test the parse.  
  780. # Returns: 
  781. # Parameters:
  782. #       fname - file to parse
  783. #       printIt - true if we should print to stdout the parse details in addition to logging to the file
  784. # Notes: This is not part of the production product.  Drive UT from external script.  Write basic results
  785. #       to std out and details to output file name derived from input file name...
  786. # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  787. def DriveParse(fname, printIt, sp):
  788.  
  789.     # instance declared in calling function -- sp = ParseScript()
  790.     printString = ""
  791.     if sp.openFile(fname):
  792.         
  793.         # create output file name and set up for logging....
  794.         nameParts = string.split(fname, '.')
  795.         outName = nameParts[0] + "OUT.txt"
  796.         outf = open(outName, 'w')
  797.         
  798.         parseResult = sp.parseScript()
  799.  
  800.         # print the summary result to stdout and the details to the output file....        
  801.         if parseResult:
  802.             # parse was good
  803.             print "filename:" + fname + "  Parse Result:good"
  804.             outf.write("filename:" + fname + "  Parse Result:good\n\n\n")
  805.             for pyblock in sp.pyBlocks:
  806.                 block, cmd = pyblock.dump()
  807.                 outf.write("Pyblock:" + cmd + "\n" + block + "\n")
  808.                 printString = printString + "Pyblock:" + cmd + "\n" + block + "\n"
  809.         else:
  810.             # parse failure
  811.             print "filename:" + fname + "  " + sp.errorString
  812.             outf.write("filename:" + fname + "  " + sp.errorString + "\n")
  813.             outf.write(sp.prevPrevLine + sp.prevLine + sp.line)
  814.             outf.write("Failing line number:" + str(sp.lineNum) + "    Failing Char Position:" + str(sp.linepos))
  815.             printString = printString + sp.prevPrevLine + sp.prevLine + sp.line
  816.             printString = printString + "Failing line number:" + str(sp.lineNum) + "    Failing Char Position:" + str(sp.linepos)
  817.  
  818.         if printIt == "dump":
  819.             print printString
  820.                                                                                                                       
  821.         outf.close()
  822.  
  823.     else:
  824.         print "failed to open file:", fname
  825.  
  826.     return sp        
  827.     
  828. ######################
  829. if __name__ == '__main__':
  830.  
  831.     sp = ParseScript()
  832.  
  833.     DriveParse(fname, "dump", sp)
  834.  
  835. ##    if len(sys.argv) == 3:
  836. ##        DriveParse(sys.argv[1], sys.argv[2])
  837. ##    elif len(sys.argv) == 2:
  838. ##        # assume no stdout dumping is wanted
  839. ##        DriveParse(sys.argv[1], "NoDump")
  840. ##    else:
  841. ##        print "Needs 2 parameters: filename and dump option.  Second parm = 'dump' to send details to stdout."
  842.